#if !defined(_HGVECTOR_H_)
#define _HGVECTOR_H_

/**
  * @author PauK
  * @param T must implements copy constructor and such operations as
  * @version 1.2
  * HGVector class that represents math vector (either point or direction).
  * If fourth element in HGVector > 0, then that is a point, if == 0 =>
  * that is a direction.
  */

#include "hgmath_global.h"

namespace HUGE
{
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
   * Vector template interface
   */
 template<typename T>
 class HGVector
 {
  public:
   HGVector(void);
   HGVector(const HGVector<T> &vec);
   explicit HGVector(T vector[4]);
   explicit HGVector(const T &x, const T &y, const T &z, const T &w);

   /**
     * Function represents dot product vector operation.
     * The w (fourth) element of the vector will be ignored.
     * @return T - scalar result of the dot product
     */
   T dot (const HGVector<T> &rhs) const;

   /**
     * Cross product vector operation.
     * The w (fourth) element of the vector will be ignored.
     * Result saved in the current HGVector.
     * @return HGVector& - reference to this HGVector, that contains result
     */
   HGVector<T>& cross(const HGVector<T> &rhs);

   /**
     * Triple (mixed) product vector operation.
     * The w (fourth) element of the vector will be ignored.
     * @return T - scalar result of the triple product
     */
   T triple(const HGVector<T> &second_vec, const HGVector<T> &third_vec) const;

   /**
     * Function computes simple vector addition and saves result in the
     * caller object (this object).
     * @return HGVector& - reference to this HGVector, that contains result
     */
   HGVector<T>& add(const HGVector<T>& rhs);

   /**
     * Simple vector substraction.
     * Result saved in the caller object (this object).
     * @return HGVector& - reference to this HGVector, that contains result
     */
   HGVector<T>& sub(const HGVector<T>& rhs);

   /**
     * Scalar multiplication.
     * Result saved in the caller object (this object).
     * @return HGVector& - reference to this HGVector, that contains result
     */
   HGVector<T>& mul(const T &rhs);

   T& x();
   T& y();
   T& z();
   T& w();
   T x() const;
   T y() const;
   T z() const;
   T w() const;
   T operator[](HGint index) const;
   T& operator[](HGint index);

   HGVector<T>& operator=(const HGVector<T> &rhs);
   HGbool operator==(const HGVector<T> &rhs) const;

   /**
     * Function computes simple vector addition.
     * @return HGVector - new HGVector, that contains result
     */
   template<typename K>
   friend HGVector<K> operator+(const HGVector<K> &lhs, const HGVector<K> &rhs);

   /**
     * Function computes simple vector substraction.
     * @return HGVector - new HGVector, that contains result
     */
   template<typename K>
   friend HGVector<K> operator-(const HGVector<K> &lhs, const HGVector<K> &rhs);

   /**
     * Simple scalar multiplication.
     * @return HGVector - new HGVector, that contains result
     */
   template<typename K>
   friend HGVector<K> operator*(const HGVector<K> &lhs, const K &rhs);

   /**
     * Vector cross product.
     * The w (fourth) element of the vector will be ignored, but
     * in the result vector w element will be equivalent to lhs
     * w element.
     * @return HGVector - new HGVector, that contains result
     */
   template<typename K>
   friend HGVector<K> cross(const HGVector<K> &lhs, const HGVector<K> &rhs);

  private:
   void __setVec(const T vector[4]);
   void __crossProduct(T res_vec[4], T lhs_vec[4], T rhs_vec[4]);

   T __elems[4];
 };
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
   * Vector template implementation.
   */
 template<typename T>
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 HGVector<T>::HGVector(void)
 {
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGVector<T>::HGVector(const HGVector<T> &vec)
 {
  __setVec(vec.__elems);
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGVector<T>::HGVector(T vector[4])
 {
  __setVec(vector);
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGVector<T>::HGVector(const T &x, const T &y, const T &z, const T &w)
 {
  __elems[0] = x;
  __elems[1] = y;
  __elems[2] = z;
  __elems[3] = w;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T HGVector<T>::dot(const HGVector<T> &rhs) const
 {
  return __elems[0] * rhs.__elems[0] +
         __elems[1] * rhs.__elems[1] +
         __elems[2] * rhs.__elems[2];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGVector<T>& HGVector<T>::cross(const HGVector<T> &rhs)
 {
  T res_vec[3];
  __crossProduct(res_vec, __elems, rhs.__elems);
  __elems[0] = res_vec[0];
  __elems[1] = res_vec[1];
  __elems[2] = res_vec[2];
  return *this;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T HGVector<T>::triple(const HGVector<T> &second_vec,
                       const HGVector<T> &third_vec) const
 {
  T res_vec[3];

  //cross product of second_vec and third_vec
  res_vec[0] = second_vec.__elems[1]*third_vec.__elems[2] -
               second_vec.__elems[2]*third_vec.__elems[1];
  res_vec[1] = second_vec.__elems[2]*third_vec.__elems[0] -
               second_vec.__elems[0]*third_vec.__elems[2];
  res_vec[2] = second_vec.__elems[0]*third_vec.__elems[1] -
               second_vec.__elems[1]*third_vec.__elems[0];

  //dot product of the res_vec and this vector
  return __elems[0] * res_vec[0] +
         __elems[1] * res_vec[1] +
         __elems[2] * res_vec[2];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGVector<T>& HGVector<T>::add(const HGVector<T>& rhs)
 {
  __elems[0] += rhs.__elems[0];
  __elems[1] += rhs.__elems[1];
  __elems[2] += rhs.__elems[2];
  __elems[3] += rhs.__elems[3];
  return *this;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGVector<T>& HGVector<T>::sub(const HGVector<T>& rhs)
 {
  __elems[0] -= rhs.__elems[0];
  __elems[1] -= rhs.__elems[1];
  __elems[2] -= rhs.__elems[2];
  __elems[3] -= rhs.__elems[3];
  return *this;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGVector<T>& HGVector<T>::mul(const T &rhs)
 {
  __elems[0] *= rhs;
  __elems[1] *= rhs;
  __elems[2] *= rhs;
  __elems[2] *= rhs;
  return *this;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T& HGVector<T>::x()
 {
  return __elems[0];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T& HGVector<T>::y()
 {
  return __elems[1];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T& HGVector<T>::z()
 {
  return __elems[2];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T& HGVector<T>::w()
 {
  return __elems[3];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T HGVector<T>::x() const
 {
  return __elems[0];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T HGVector<T>::y() const
 {
  return __elems[1];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T HGVector<T>::z() const
 {
  return __elems[2];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T HGVector<T>::w() const
 {
  return __elems[3];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T HGVector<T>::operator[](HGint index) const
 {
  hgAssert(index >= 0 && index <= 3);

  return __elems[index];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 T& HGVector<T>::operator[](HGint index)
 {
  hgAssert(index >= 0 && index <= 3);

  return __elems[index];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGVector<T>& HGVector<T>::operator=(const HGVector<T> &rhs)
 {
  if(this != &rhs)
   __setVec(rhs.__elems);
  return *this;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 HGbool HGVector<T>::operator==(const HGVector<T> &rhs) const
 {
  return __elems[0] == rhs.__elems[0] &&
         __elems[1] == rhs.__elems[1] &&
         __elems[2] == rhs.__elems[2] &&
         __elems[3] == rhs.__elems[3];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename K>
 HGVector<K> operator+(const HGVector<K> &lhs, const HGVector<K> &rhs)
 {
  HGVector<K> res_vec;

  res_vec.__elems[0] = lhs.__elems[0] + rhs.__elems[0];
  res_vec.__elems[1] = lhs.__elems[1] + rhs.__elems[1];
  res_vec.__elems[2] = lhs.__elems[2] + rhs.__elems[2];
  res_vec.__elems[3] = lhs.__elems[3] + rhs.__elems[3];

  return res_vec;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename K>
 HGVector<K> operator-(const HGVector<K> &lhs, const HGVector<K> &rhs)
 {
  HGVector<K> res_vec;

  res_vec.__elems[0] = lhs.__elems[0] - rhs.__elems[0];
  res_vec.__elems[1] = lhs.__elems[1] - rhs.__elems[1];
  res_vec.__elems[2] = lhs.__elems[2] - rhs.__elems[2];
  res_vec.__elems[3] = lhs.__elems[3] - rhs.__elems[3];

  return res_vec;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename K>
 HGVector<K> operator*(const HGVector<K> &lhs, const K &rhs)
 {
  HGVector<K> res_vec;

  res_vec.__elems[0] = lhs.__elems[0] * rhs;
  res_vec.__elems[1] = lhs.__elems[1] * rhs;
  res_vec.__elems[2] = lhs.__elems[2] * rhs;
  res_vec.__elems[3] = lhs.__elems[3] * rhs;

  return res_vec;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename K>
 HGVector<K> cross(const HGVector<K> &lhs, const HGVector<K> &rhs)
 {
  HGVector<K> res_vec;

  __crossProduct(res_vec.__elems, lhs.__elems, rhs.__elems);

  return res_vec;
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 void HGVector<T>::__setVec(const T vector[4])
 {
  __elems[0] = vector[0];
  __elems[1] = vector[1];
  __elems[2] = vector[2];
  __elems[3] = vector[3];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 template<typename T>
 void HGVector<T>::__crossProduct(T res_vec[4], T lhs_vec[4], T rhs_vec[4])
 {
  res_vec[0] = lhs_vec[1]*rhs_vec[2] - lhs_vec[2]*rhs_vec[1];
  res_vec[1] = lhs_vec[2]*rhs_vec[0] - lhs_vec[0]*rhs_vec[2];
  res_vec[2] = lhs_vec[0]*rhs_vec[1] - lhs_vec[1]*rhs_vec[0];
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
};

#endif // _HGVECTOR_H_
